import fetch from 'dva/fetch';
import { notification } from 'antd';
import { stringify } from 'querystring';

const codeMessage = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队（异步任务）。',
  204: '删除数据成功。',
  400: '发出的请求有错误，服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限（令牌、用户名、密码错误）。',
  403: '用户得到授权，但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录，服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除，且不会再得到的。',
  422: '当创建一个对象时，发生一个验证错误。',
  500: '服务器发生错误，请检查服务器。',
  502: '网关错误。',
  503: '服务不可用，服务器暂时过载或维护。',
  504: '网关超时。',
};

interface defaultOptions {
  credentials?: String;
  mode: String;
}

function checkStatus(response: any) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }
  const errortext = codeMessage[response.status] || response.statusText;
  notification.error({
    message: `请求错误 ${response.status}: ${response.url}`,
    description: errortext,
  });
  const error: any = new Error(errortext);
  error.name = response.status;
  error.response = response;
  throw error;
}

const request = (
  url: string,
  options?: any,
  isFormData = false,
  // contentType = 'application/x-www-form-urlencoded',
  contentType = 'application/json',
) => {
  const defaultOptions: defaultOptions = {
    mode: 'cors',
  };
  let myHeaders = new Headers({
    'Access-Control-Allow-Origin': '*',
    'Content-Type': contentType,
  });
  if (!isFormData) {
    if (
      url.indexOf('weatherInfo') !== -1 ||
      url.indexOf('10800') !== -1 ||
      url.indexOf('7080') !== -1
    ) {
      myHeaders = new Headers({
        'Content-Type': contentType,
        // 'token': sessionStorage.getItem('ACJSESSIONID') || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50IjoibHJ5IiwiZXhwIjoxNTcxMjA5OTU4LCJpYXQiOjE1NzA2MDUxNTh9.eQpplPri0yBLBMOsbeipK6KQfk-c_MXp2fw0hyanYkA',
        // 'hostjava': sessionStorage.getItem('ACJSESSIONID') || 'http://120.25.225.254:8082',
        // 'tokenjava': sessionStorage.getItem('ACJSESSIONID') || 'eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1Njk2NzA2MDEsInN1YiI6ImxyeSIsImNyZWF0ZWQiOjE1Njk2MzQ2MDE1MDF9.cXB-4X7EgnNhkWgF7CRIiUU2UVOZML00KDYnmbLs0Bc',
        'Access-Control-Allow-Origin': '*',
        Accept: 'application/json',
      });
    } else {
      myHeaders = new Headers({
        'Access-Control-Allow-Origin': '*',
        Accept: 'application/json',
        'Content-Type': contentType,
        // 'content': "multipart/form-data",
      });
      defaultOptions.credentials = 'include';
    }
  }

  const newOptions = { ...defaultOptions, ...options, headers: myHeaders };
  if (newOptions.method === 'POST' || newOptions.method === 'PUT') {
    if (!isFormData && contentType !== 'application/json') {
      newOptions.body = stringify(newOptions.body);
    } else if (isFormData === 'formData') {
      newOptions.body = parseParamFromData(newOptions.body);
    }
  }

  //todo 拼接url地址
  let fullUrl = url;
  if (options && options.params) {
    let data_arr = [];
    for (let key in options.params) {
      data_arr.push(key + '=' + options.params[key]);
    }
    fullUrl += '?' + data_arr.join('&');
  }

  return fetch(fullUrl, newOptions)
    .then(checkStatus)
    .then(response => {
      const { status } = response;
      if (status === '403') {
        window.location.hash = '/user/login';
      }
      return response.json();
    })
    .then(json => {
      return json;
    })
    .catch(error => {
      if (error.code) {
        notification.error({ message: error.name, description: error.message });
      }
      if ('stack' in error && 'message' in error) {
        notification.error({
          message: `请求错误: ${fullUrl}`,
          description: error.message,
        });
      }
      console.log(error, '请求错误');
      return error;
    });
};

export default request;


// ----------------------   Example POST method implementation  -----------------------
postData('http://example.com/answer', { answer: 42 })
  .then(data => console.log(data)) // JSON from `response.json()` call
  .catch(error => console.error(error))

function postData(url: any, data: any) {
  // Default options are marked with *
  return fetch(url, {
    cache: 'no-cache',                           // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin',                 // include, same-origin, *omit 
    //发送带凭据的请求
    //为了让浏览器发送包含凭据的请求（即使是跨域源），要将credentials: 'include'添加到传递给 fetch()方法的init对象。
    body: JSON.stringify(data),                 // must match 'Content-Type' header
    headers: {
      'user-agent': 'Mozilla/4.0 MDN Example',
      'content-type': 'application/json;charset=utf-8'   //上传 JSON 数据
    },
    method: 'POST',                             // *GET, POST, PUT, DELETE, etc.
    mode: 'cors',                               // no-cors, cors, *same-origin
    redirect: 'follow',                         // manual, *follow, error
    referrer: 'no-referrer',                    // *client, no-referrer
  })
    .then(response => response.json()) // parses response to JSON
}


// 上传文件
// 可以通过HTML<input type="file" />元素，FormData() 和fetch()上传文件
var formData = new FormData()
var fileField: any = document.querySelector("input[type='file']")
formData.append('username', 'abc123')
formData.append('avatar', fileField.files[0])

fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
  .then(response => response.json())
  .catch(error => console.error('Error:', error))
  .then(response => console.log('Success:', response))

// -------------------------------  上传多个文件 ------------------------
// 可以通过HTML<input type="file" mutiple/>元素，FormData() 和fetch()上传文件。
var formData = new FormData()
var photos: any = document.querySelector("input[type='file'][multiple]")
formData.append('title', 'My Vegas Vacation')
formData.append('photos', photos.files)

fetch('https://example.com/posts', {
  method: 'POST',
  body: formData
})
  .then(response => response.json())
  .then(response => console.log('Success:', JSON.stringify(response)))
  .catch(error => console.error('Error:', error))


// ------------------------- 检测请求是否成功 ---------------------------------
// 如果遇到网络故障，fetch() promise 将会 reject，带上一个 TypeError 对象。
// 虽然这个情况经常是遇到了权限问题或类似问题——比如 404 不是一个网络故障。想要精确的判断 fetch() 是否成功，
// 需要包含 promise resolved 的情况，此时再判断 Response.ok 是不是为 true。类似以下代码：

fetch('flowers.jpg')
  .then(function (response) {
    if (response.ok) { return response.blob(); }
    throw new Error('Network response was not ok.');
  })
  .then(function (myBlob) {
    var objectURL = URL.createObjectURL(myBlob);
    // URL.revokeObjectURL(objectURL) //为了获得最佳性能和内存使用状况，你应该在合适时机（关闭浏览器，卸载）主动释放掉它们
    myImage.src = objectURL;
  })
  .catch(function (error) {
    console.log('There has been a problem with your fetch operation: ', error.message);
  });

// ---------------------- 自定义请求对象 -------------------------------
// 除了传给 fetch() 一个资源的地址，你还可以通过使用 Request() 构造函数来创建一个 request 对象，然后再作为参数传给 fetch()：

var myHeaders = new Headers();
var myInit = {
  method: 'GET',
  headers: myHeaders,
  mode: 'cors',
  cache: 'default'
};

var myRequest = new Request('flowers.jpg', myInit);
fetch(myRequest)
  .then(function (response) {
    return response.blob();   //todo blob() 方法将返回一个promise，使用一个 Blob 解决
  })
  .then(function (myBlob) {
    var objectURL = URL.createObjectURL(myBlob);
    myImage.src = objectURL;
  });
// Request() 和 fetch() 接受同样的参数 你甚至可以传入一个已存在的 request 对象来创造一个拷贝

// ----------------------- Headers --------------------------------------------
// 使用 Headers 的接口，你可以通过 Headers() 构造函数来创建一个你自己的 headers 对象。一个 headers 对象是一个简单的多名值对：
var myHeaders = new Headers();
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Content-Length", "Hello World".length.toString());
myHeaders.append("X-Custom-Header", "ProcessThisImmediately");
// 也可以传一个多维数组或者对象字面量：
myHeaders = new Headers({
  "Content-Type": "text/plain",
  "Content-Length": "Hello World".length.toString(),
  "X-Custom-Header": "ProcessThisImmediately",
});

// 它的内容可以被获取：
console.log(myHeaders.has("Content-Type")); // true
console.log(myHeaders.has("Set-Cookie")); // false
myHeaders.set("Content-Type", "text/html");
myHeaders.append("X-Custom-Header", "AnotherValue");
console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.getAll("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]
myHeaders.delete("X-Custom-Header");
console.log(myHeaders.getAll("X-Custom-Header")); // [ ]

// 虽然一些操作只能在 ServiceWorkers 中使用，但是它提供了更方便的操作 Headers 的 API。
// 如果使用了一个不合法的HTTP Header属性名，那么Headers的方法通常都抛出 TypeError 异常。
// 如果不小心写入了一个不可写的属性，也会抛出一个 TypeError 异常。除此以外的情况，失败了并不抛出异常。例如：
var myResponse = Response.error();
try {
  myResponse.headers.set("Origin", "http://mybank.com");
} catch (e) {
  console.log("Cannot pretend to be a bank!");
}

// 最佳实践是在使用之前检查 content type 是否正确，比如：
fetch('url地址')
  .then(function (response) {
    if (response.headers.get("content-type") === "application/json") {
      return response.json().then(function (json) {
        // process your JSON further 进一步处理json
      });
    } else {
      console.log("Oops, we haven't got JSON!");
    }
  });


// Guard
// 由于 Headers 可以在 request 请求中被发送或者在 response 请求中被接收，并且规定了哪些参数是可写的，Headers 对象有一个特殊的 guard 属性。
// 这个属性没有暴露给 Web，但是它影响到哪些内容可以在 Headers 对象中被操作。
// 可能的值如下：
// none：默认的
// request：从 request 中获得的 headers（Request.headers）只读
// request-no-cors：从不同域（Request.mode no-cors）的 request 中获得的 headers 只读
// response：从 response 中获得的 headers（Response.headers）只读
// immutable：在 ServiceWorkers 中最常用的，所有的 headers 都只读。
// 注意：你不可以添加或者修改一个 guard 属性是 request 的 Request Headers 的 Content-Length 属性。同样地，插入 Set-Cookie 属性到一个 response headers 是不允许的，因此 ServiceWorkers 是不能给合成的 Response 的 headers 添加一些 cookies。


// ---------------------------------- Response 对象 -----------------------------------
// 如上述, Response 实例是在 fetch() 处理完promises之后返回的。
你会用到的最常见的response属性有:
Response.status         //整数(默认值为200) 为response的状态码.
Response.statusText     // 字符串(默认值为"OK"),该值与HTTP状态码消息对应.
Response.ok            //如上所示, 该属性是来检查response的状态是否在200-299(包括200,299)这个范围内.该属性返回一个Boolean值.
// 它的实例也可用通过 JavaScript 来创建, 但只有在ServiceWorkers中才真正有用,当使用respondWith()方法并提供了一个自定义的response来接受request时:
var myBody = new Blob();
addEventListener('fetch', function (event) {
  event.respondWith(new Response(myBody, {
    headers: { "Content-Type": "text/plain" }
  });
});
// Response() 构造方法接受两个可选参数—response的数据体和一个初始化对象(与Request()所接受的init参数类似.)


// ----------------------------------  Body -----------------------------------

// 不管是请求还是响应都能够包含body对象. body也可以是以下任意类型的实例.

// ·ArrayBuffer
// ·ArrayBufferView (Uint8Array等)
// ·Blob/File
// ·string
// ·URLSearchParams
// ·FormData
// ·Body 类定义了以下方法 (这些方法都被 Request 和Response所实现)以获取body内容. 这些方法都会返回一个被解析后的Promise对象和数据.

// ·arrayBuffer()
// ·blob()
// ·json()
// ·text()
// ·formData()
// 比起XHR来，这些方法让非文本化的数据使用起来更加简单。

// 请求体可以由传入body参数来进行设置:

var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
  method: "POST",
  body: form
})
// request和response（包括fetch() 方法）都会试着自动设置Content-Type。如果没有设置Content-Type值，发送的请求也会自动设值。